﻿using DotNetty.Wraper;
using DotNetty.Codecs.Http;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using System.Threading.Tasks;
using System;

namespace DotNetty.Wraper
{
    class WebSocketClientBuilder : 
        BaseGenericClientBuilder<IWebSocketClientBuilder, IWebSocketClient, string>
        , IWebSocketClientBuilder
    {
        public WebSocketClientBuilder(ClientOption _option)
            : base(_option)
        {
            if (option == null) throw new ArgumentNullException("option");
            if (string.IsNullOrWhiteSpace(_option.Path)) 
                _option.Path = "/";
        }

        public async override Task<IWebSocketClient> BuildAsync(Action<Transport.Channels.IChannelPipeline> OnPipelineAction = null, Func<IWebSocketClient, BaseSimpleChannelInboundHandler<object>> addChannelHandler = null)
        {
            //Environment.SetEnvironmentVariable("io.netty.allocator.numDirectArenas", "0");
            //Environment.SetEnvironmentVariable("io.netty.allocator.numHeapArenas", "0");
            string targetHost = null;
            if (!string.IsNullOrWhiteSpace(option.Certificate) && System.IO.File.Exists(option.Certificate))
            {
                var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(option.Certificate, option.CertificatePassword);
                targetHost = cert.GetNameInfo(System.Security.Cryptography.X509Certificates.X509NameType.DnsName, false);
            }
            var tcpClient = new WebSocketClient(!string.IsNullOrWhiteSpace(targetHost), option.SeverIp, option.Port, option.Path, _event);
            IEventLoopGroup group;
            if (option.UseLibuv)
            {
                group = new Transport.Libuv.EventLoopGroup();
            }
            else
            {
                group = new MultithreadEventLoopGroup();
            }
            var _bootstrap = new Bootstrap();
            if (option.TcpNodelay)
            {
                _bootstrap.Option(ChannelOption.TcpNodelay, true);
            }
            if (option.SoKeepalive)
            {
                _bootstrap.Option(ChannelOption.SoKeepalive, true);
            }
            if (option.ConnectTimeout > 0)
            {
                _bootstrap.Option(ChannelOption.ConnectTimeout, TimeSpan.FromMilliseconds(option.ConnectTimeout));
            }
            var clientChannel = await _bootstrap//new Bootstrap()
                .Group(group)
                .Channel<TcpSocketChannel>()
                .Option(ChannelOption.TcpNodelay, true)
                .Handler(new ActionChannelInitializer<IChannel>(channel =>
                {
                    IChannelPipeline pipeline = channel.Pipeline;
                    if (!string.IsNullOrWhiteSpace(targetHost))
                    {
                        pipeline.AddLast(new Handlers.Tls.TlsHandler(stream => new System.Net.Security.SslStream(stream, true, (sender, certificate, chain, errors) => true), new Handlers.Tls.ClientTlsSettings(targetHost)));
                    }
                    pipeline.AddLast(
                        new HttpClientCodec(),
                        new HttpObjectAggregator(8192));

                    OnPipelineAction?.Invoke(pipeline);

                    if (addChannelHandler != null)
                        pipeline.AddLast(addChannelHandler(tcpClient));
                    else
                        pipeline.AddLast(new CommonChannelHandler(tcpClient));
                })).ConnectAsync($"{option.SeverIp}:{option.Port}".ToIPEndPoint());

            await tcpClient.HandshakeCompletion;

            tcpClient.SetChannel(clientChannel);
            tcpClient.AfterClose = () =>
            {
                group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(option.QuietPeriod), TimeSpan.FromSeconds(option.ShutdownTimeout)).Wait();
            };
            return await Task.FromResult(tcpClient);
        }
    }
}
